#define _BSD_SOURCE 0

#include <sys/types.h>
#include <ifaddrs.h>
#include <pcap.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "connection.h"
#include <stdio.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <sys/time.h>

#define VERBOSE 0

/* Function definitions */
void handle_ether(struct ether_header ** infoheader,const u_char *packet, struct pcap_pkthdr header);
void handle_ip(struct ip ** infoheader,const u_char *packet, struct pcap_pkthdr header);
void handle_tcp(struct tcphdr ** infoheader,const u_char *packet, struct pcap_pkthdr header);
int inSubnet(u_int ip, bpf_u_int32 net);
void initializeConnection(u_int ip_source,u_int ip_destination, int i);
void removeSequenceEntry(int,int);


//Change the filter_exp variable to change the filter. The full set can be looked up.
//Change the device in the dev variable. just choose a good network interface.
//This must be run with admin privileges. Be sure to include -lpcap in the gcc call. I installed libpcap-dev.


struct ip_addr {
    unsigned char one;
    unsigned char two;
    unsigned char three;
    unsigned char four;
};

//Code for tracking connections. This allows up to 256 devices
Connection * connections;
int devices = 0;

int main(int argc, char *argv[]) {
  pcap_t *handle;           /* Session handle */
  char *dev;            /* The device to sniff on */
  char errbuf[PCAP_ERRBUF_SIZE];    /* Error string */
  struct bpf_program fp;        /* The compiled filter */
  /* This filter will only give us TCP packets */
  char filter_exp[] = "tcp";    /* The filter expression */
  bpf_u_int32 mask;     /* Our netmask */
  bpf_u_int32 net;      /* Our IP */
  struct pcap_pkthdr header;    /* The header that pcap gives us */
  const u_char *packet;     /* The actual packet */
  u_char * ptr;
  int i = 0;
  connections = (Connection *)(malloc(256*sizeof(Connection))); /* list of open connections */
  
  
  
  char *payload; /* packet payload */
  
  /* Unneeded
   * u_int size_ip;
   * u_int size_tcp;
   */
  
  /* input for what device to use.
   * Uses commandline input because we were getting mixed results on our 
   * different machines. 
   */
  dev = argv[1];//pcap_lookupdev(errbuf);
  /* Find the properties for the device */
  if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
    fprintf(stderr, "Couldn't get netmask for device %s: %s\n", dev, errbuf);
    net = 0;
    mask = 0;
  }
  /* Open the session in promiscuous mode */
  handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
  if (handle == NULL) {
    fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
    return(2);
  }
  /* Compile and apply the filter */
  if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
    fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
    return(2);
  }
  /* Set the filter */
  if (pcap_setfilter(handle, &fp) == -1) {
    fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
    return(2);
  }
  
  
  /* Grab a packet */
  while(1) {
    struct ether_header * ethernet;
    struct ip *ip_header;
    struct tcphdr *tcp;
    packet = pcap_next(handle, &header);
    
    /* Put all of this into a callback function later */
    if(packet == NULL) {
      if (VERBOSE)
        printf("Didn't grab packet\n");
      } else {
        /* Print its length */
        if (VERBOSE) {
          printf("Jacked a packet with length of [%d]\n", header.len);
          printf("Ethernet address length is %d\n", ETHER_HDR_LEN);
        }
        handle_ether(&ethernet,packet, header);
        handle_ip(&ip_header,packet, header);
        handle_tcp(&tcp,packet, header);
      
      
        /* If it's an IP packet, get the source and destination */ 
        if (ntohs(ethernet->ether_type) == ETHERTYPE_IP) {
          u_int ip_destination = ip_header->ip_dst.s_addr;
          u_int ip_source = ip_header->ip_src.s_addr;
    
          /* Get the tcp information */
          u_short sport = tcp->th_sport;
          u_short dport = tcp->th_dport;
          tcp_seq seq_num = tcp->th_seq;
          tcp_seq ack_num = tcp->th_ack;
          u_short window = tcp->th_win;

          /* Figure out if we're sending/uploading */
          int sending = inSubnet(ip_source, net);
          if (tcp->th_flags & TH_ACK) {
            /* A user of the router is the source */
            if (1) {
              int choice = -1;
              /* Find out the sending device */
              for (i = 0; i < devices; i++) {
                if ((connections[i].client == ip_source && connections[i].server == ip_destination) ||(connections[i].client == ip_destination && connections[i].server == ip_source)) {
                  choice = i;
                }
              }
              //printf("choice == %d", choice);
              if (choice == -1 && devices <= 256) {
                devices += 1;
                i = devices -1;
                choice = i;
                initializeConnection(ip_source,ip_destination,i);
              } 
            /* Store connection data if there are 256 or less devices */
	      if (devices <= 256 && header.len > 100) { 
		if (connections[choice].waiting == NULL)
		  connections[choice].waiting = (Sequence *)malloc(200*sizeof(Sequence));
		
	      
		int index = connections[choice].watching -1;
		Sequence * sequ = connections[choice].waiting;
		if (connections[choice].watching ==1) {
		  
		  connections[choice].watching = 2;
		  struct timeval time;
		  gettimeofday(&time, NULL);
		  sequ[index].startTime = 0;
		  sequ[index].status = 1;
		  sequ[index].startTime = time.tv_usec/1000.0;
		  sequ[index].sequence = 0;
		  sequ[index].next_sequence = seq_num+(header.len-66);
		  sequ[index].size = header.len + 1;
	      }
	    
	    }
        
            } if (1){ /* Recieving a packet/downloading */
              int choice = -1;
              for (i = 0; i < devices; i++) {
                if ((connections[i].client == ip_source && connections[i].server == ip_destination) ||(connections[i].client == ip_destination && connections[i].server == ip_source)) {
                  choice = i;
                }
              }

              
	      int i = 2;
		if (connections[choice].waiting[i].next_sequence == ack_num && connections[choice].watching == 2) {
                    struct timeval time;
                    gettimeofday(&time, NULL);
                    float rtt = time.tv_usec/1000.0 - connections[choice].waiting[i].startTime;
                    //removeSequenceEntry(choice,i);
                    connections[choice].watching = 1;
		    if (rtt > 2) {
		      printf("Flags: %x, rtt is %f\n",tcp->th_flags,rtt);
		      
		      float bandwidth = (header.len+connections[choice].waiting[i].size)/rtt;
		      bandwidth = (bandwidth *8)/1000;
		      printf("Effective Bandwidth: %f Mb/s\n",bandwidth);
		    }
		    
                  }
               
              
            }
          }
	}
    
      free(ethernet);
      free(ip_header);
      free(tcp);
      /* End callback function  */
    } 
    }
  
  /* And close the session */
  pcap_close(handle);
  return(0);
  
}
void removeSequenceEntry(int connectionIndex,int index) {
  index += 1;
  for (;index < connections[connectionIndex].watching;index++) {
    Sequence * list = connections[connectionIndex].waiting;
    list[index-1].status = list[index].status;
    list[index-1].startTime = list[index].startTime;
    list[index-1].sequence = list[index].sequence;
    list[index-1].next_sequence = list[index].next_sequence;
  }
  connections[connectionIndex].watching -= 1;
}
int inSubnet(u_int ip,bpf_u_int32 net) {
  struct ip_addr *s_net = (struct ip_addr *)&net;
  struct ip_addr *s_ip = (struct ip_addr *)&ip;
  //printf("Subnet: %d.%d.%d\n",s_net->one,s_net->two,s_net->three);
  //printf("ip: %d.%d.%d\n",s_ip->one,s_ip->two,s_ip->three);
  if (s_net->one == s_ip->one)
    if (s_net->two == s_ip->two)
      return 1;
  return 0;
}

 void initializeConnection(u_int ip_source, u_int ip_destination, int i) {

   connections[i].client = ip_source;
   connections[i].server = ip_destination;
   connections[i].avgrtt = -1;
   connections[i].watching = 0;
   connections[i].waiting = NULL;
 }

void handle_ether(struct ether_header ** eth,const u_char *packet, struct pcap_pkthdr header) {
    u_char *ptr; /* for printing out hardware info */
    int i;
    
    /* start with ether header */
    *eth = (struct ether_header *)malloc(sizeof(struct ether_header));
    struct ether_header *ethernet = ((struct ether_header *) packet);

    memcpy(*eth, ethernet,sizeof(struct ether_header));

    /* Do a couple of checks to see what type of packet we have */
    if (ntohs(ethernet->ether_type) == ETHERTYPE_IP)
    {

      if (VERBOSE)
        printf("Ethernet type hex:%x dec%d is an IP packet\n",
               ntohs(ethernet->ether_type),
               ntohs(ethernet->ether_type));
    }else if (ntohs(ethernet->ether_type) == ETHERTYPE_ARP)
    {
      if (VERBOSE)
        printf("Ethernet type hex:%x dec:%d is an ARP packet\n",
               ntohs(ethernet->ether_type),
               ntohs(ethernet->ether_type));
    }else
    {
      if (VERBOSE)
        printf("Ethernet type %x not IP", ntohs(ethernet->ether_type));
        
    }
    
    ptr = ethernet->ether_dhost;
    i = ETHER_ADDR_LEN;
    if (VERBOSE) {
      printf(" Destination Address:  ");
      do{
        printf("%s%x", (i == ETHER_ADDR_LEN) ? " " : ":", *ptr++);
      }while(--i>0);
    }
    if (VERBOSE)
      printf("\n");
    
    ptr = ethernet->ether_shost;
    i = ETHER_ADDR_LEN;
    if (VERBOSE) {
      printf(" Source Address:  ");
      do{
        printf("%s%x", (i == ETHER_ADDR_LEN) ? " " : ":", *ptr++);
      }while(--i>0);
      printf("\n");
    }
}

void handle_ip(struct ip ** ip_h,const u_char *packet, struct pcap_pkthdr header) {
    
    int len, length;
    u_int hlen, version, off;
    
    *ip_h = (struct ip *)malloc(sizeof(struct ip));
     
    /* Start IP stuff */
    length = header.len - sizeof(struct ether_header);
    struct ip * ip_header = (struct ip*)(packet + sizeof(struct ether_header));
    
    memcpy(*ip_h, ip_header,sizeof(struct ip));

    /* check packet is valid length */
    if (length < sizeof(struct ip))
    {
      if (VERBOSE)
        printf("truncated ip %d", length);
    }
    
    len = ntohs(ip_header->ip_len);
    hlen = (ip_header->ip_hl);
    version = (ip_header->ip_v);
    
    if(version != 4)
    {
      if (VERBOSE)
        printf("Unknown version %d\n", version);
    }
    if(hlen < 5)
    {
      if (VERBOSE)
        printf("bad-hlen %d \n", hlen);
    }
    /* see if we have as much packet as we should */
    if(length < len) {
      if (VERBOSE)
        printf("\ntruncated IP - %d bytes missing\n", len-length);
    }
    /* Check to see if we have first fragment */
    off = ntohs(ip_header->ip_off);
    if((off & 0x1fff) == 0 && VERBOSE)/*no 1s in first 13 bits*/
    { /*print SOURCE DESTINATION hlen version len offset */
        printf("IP: ");
        printf("%s ", inet_ntoa(ip_header->ip_src));
        printf("%s %d %d %d %d\n",
                inet_ntoa(ip_header->ip_dst),
                hlen,version,len,off);
    }
}

void handle_tcp(struct tcphdr **tcp_h,const u_char *packet, struct pcap_pkthdr header) {
    
    u_short sport, dport, window, checksum, urgent;
    tcp_seq seq_num, ack_num;
    u_char offset, flags;
    int length;
    
    *tcp_h = (struct tcphdr *)malloc(sizeof(struct tcphdr));

    /* Start with TCP stuff */
    struct tcphdr * tcp = (struct tcphdr*)(packet + sizeof(struct ether_header) + sizeof(struct ip));
    
    memcpy(*tcp_h, tcp,sizeof(struct tcphdr));
    
    length = header.len - (sizeof(struct ether_header) + sizeof(struct ip));
    if (VERBOSE)
      printf("length %d, tcp length %d\n", length, sizeof(struct tcphdr));
    if(length < sizeof(struct tcphdr))
    {
      if (VERBOSE)
        printf("truncated tcp %d", length);
    }
    
    sport = tcp->th_sport;
    dport = tcp->th_dport;
    seq_num = tcp->th_seq;
    ack_num = tcp->th_ack;
    offset = tcp->th_off;
    flags = tcp->th_flags;
    window = tcp->th_win;
    checksum = tcp->th_sum;
    urgent = tcp->th_urp;
    if (VERBOSE) {
      printf("%x\n", flags);
      /*printf("%x\n",TH_SYN);
      //{
      printf("Source port: %d\n", sport);
      printf("Dest port: %d\n", dport);
      printf("Seq number: %d\n", seq_num);
      printf("Ack number: %d\n", ack_num);
    
      if (flags & TH_ACK) {
        printf("This is an ACK\n");
      } else {
        printf("This is not an ACK\n");
      }
      printf("Data offset: %d\n", offset * 8);
      printf("Window: %d\n", window);
      printf("Checksum: %d\n", checksum);
      printf("Urgent ptr: %d\n", urgent);
      //}*/
    }

}
